/*
 * @copyright: Huang Ding
 * @Author: ding-cx
 * @Date: 2022-04-21 10:52:38
 * @LastEditors: ding
 * @LastEditTime: 2022-07-21 17:23:21
 * @Description: file content
 */

import { Application } from "egg";
import { Dingtalk } from "../dingtalk";
import CodoBase from "../../CodoBase";

const redisKey = {
	ovAccessToken: (appkey: string) => `dingtalk:ovAccessToken:${appkey}`,
	nvAccessToken: (appkey: string) => `dingtalk:nvAccessToken:${appkey}`,
	ssoAccessToken: (appkey: string) => `dingtalk:ssoAccessToken:${appkey}`,
};

/** 钉钉token服务 */
export default class TokenService extends CodoBase {
	constructor(public app: Application, public readonly config: Dingtalk.AppConfig) {
		super(app);
	}

	/** 获取旧版token 外部可调用的，自动redis缓存及失效二次获取 */
	async getOVAccessToken(isForceRefresh = false) {
		const appkey = this.config.clientId;
		// const appsecret = this.config.clientSecret;

		// 存储token到redis中
		const key = this.getRedisKey(redisKey.ovAccessToken(appkey));
		if (!isForceRefresh) {
			const cacheValue = await this.getRedisObject<{ accessToken: string; expireIn: number }>(key);
			if (cacheValue) {
				return cacheValue.accessToken;
			}
		}
		const otoken = await this.ov_getAccessToken();
		await this.setRedisObject(
			key,
			{
				accessToken: otoken.access_token,
				expireIn: otoken.expires_in,
			},
			otoken.expires_in
		);
		return otoken.access_token;
	}

	/** 获取新版token 外部可调用的，自动redis缓存及失效二次获取 */
	async getNVAccessToken(isForceRefresh = false) {
		const appkey = this.config.clientId;
		// const appsecret = this.config.clientSecret;

		// 存储token到redis中
		const key = this.getRedisKey(redisKey.nvAccessToken(appkey));
		if (!isForceRefresh) {
			const cacheValue = await this.getRedisObject<{ accessToken: string; expireIn: number }>(key);
			if (cacheValue) {
				return cacheValue.accessToken;
			}
		}
		const otoken = await this.nv_getAccessToken();
		await this.setRedisObject(
			key,
			{
				accessToken: otoken.accessToken,
				expireIn: otoken.expireIn,
			},
			otoken.expireIn
		);
		return otoken.accessToken;
	}

	/** 获取sso token 外部可调用的，自动redis缓存及失效二次获取 */
	async getSSOAccessToken(isForceRefresh = false) {
		const appkey = this.config.corpId;
		// const appsecret = this.config.ssoSecret;

		// 存储token到redis中
		const key = this.getRedisKey(redisKey.ssoAccessToken(appkey));
		if (!isForceRefresh) {
			const cacheValue = await this.getRedisObject<{ accessToken: string; expireIn: number }>(key);
			if (cacheValue) {
				return cacheValue.accessToken;
			}
		}
		const token = await this._getSSOAccessToken();
		await this.setRedisObject(
			key,
			{
				accessToken: token.accessToken,
				expireIn: token.expireIn,
			},
			token.expireIn
		);
		return token.accessToken;
	}

	/**
	 *旧版规范 获取access_token
	 *
	 * @param {string} appkey
	 * @param {string} appsecret
	 * @return {*}
	 * @memberof PlatformDingtalkBaseTokenService
	 * - https://open.dingtalk.com/document/orgapp-server/obtain-orgapp-token
	 */
	async ov_getAccessToken() {
		const appkey = this.config.clientId;
		const appsecret = this.config.clientSecret;

		if (!appkey || !appsecret) {
			throw new Error("参数appkey或appsecret缺少！");
		}
		const url = `https://oapi.dingtalk.com/gettoken?appkey=${appkey}&appsecret=${appsecret}`;
		const res = await this.curl<Dingtalk.ov.IResponseGetAccessToken>(url, {
			contentType: "json",
			dataType: "json",
			method: "GET",
		});
		this.ovcheckError(url, res);
		return res.data;
	}

	/**
	 *新版规范获取accesstoken
	 *
	 * @param {string} appkey
	 * @param {string} appsecret
	 * @return {*}
	 * @memberof PlatformDingtalkBaseTokenService
	 */
	async nv_getAccessToken() {
		const appkey = this.config.clientId;
		const appsecret = this.config.clientSecret;
		if (!appkey || !appsecret) {
			throw new Error("参数appkey或appsecret缺少！");
		}
		const url = `https://api.dingtalk.com/v1.0/oauth2/accessToken`;
		const res = await this.curl<Dingtalk.nv.IResponseNewVersionGetAccessToken>(url, {
			method: "POST",
			dataType: "json",
			contentType: "json",
			data: {
				appKey: appkey,
				appSecret: appsecret,
			},
		});
		this.nvcheckError(url, res);
		return res.data as Dingtalk.nv.IResponseNewVersionGetAccessTokenSuccess;
	}

	/** 获取应用后台免登的access_token，注意此处的clientId和secret与普通的自建应用不同。
	 * - 新版接口
	 * 获取的token只适用于后台应用免登获取钉钉后台用户信息的功能
	 * 就是自己开发的后台集成钉钉后台，可以从钉钉sso登录自己的后台，无需再次手动输入账号密码
	 */
	async _getSSOAccessToken() {
		const corpid = this.config.clientId;
		const ssoSecret = this.config.clientSecret;

		if (!corpid || !ssoSecret) {
			throw new Error("参数corpid或ssoSecret缺少！");
		}
		const url = `https://api.dingtalk.com/v1.0/oauth2/ssoAccessToken`;
		const res = await this.curl<Dingtalk.nv.IResponseNewVersionGetAccessToken>(url, {
			method: "POST",
			dataType: "json",
			contentType: "json",
			data: {
				corpid,
				ssoSecret,
			},
		});
		this.nvcheckError(url, res);
		return res.data as Dingtalk.nv.IResponseNewVersionGetAccessTokenSuccess;
	}

	/**
	 *
	 *旧版规范 判断钉钉接口返回的结果，是否调用错误。errcode大于0
	 *
	 * @param {*} url
	 * @param {dingTalk.IResponseCommon} data
	 * @memberof DingTalkBasePublicService
	 */
	private ovcheckError(url, res: { data: Dingtalk.ov.IResponseCommon; status: number }) {
		if (res.status !== 200) {
			this.app.coreLogger.error("dingTalk 请求响应状态非200 接口url %s", url);
			throw new Error(`dingTalk 请求接口 ${url} 响应异常！`);
		}
		if (res.data.errcode !== 0) {
			this.app.coreLogger.error(
				"dingtalk旧版接口返回errcode !== 0  \r\n请求的url：%s \r\n错误码：%s \r\n错误原因：%s",
				url,
				res.data.errcode,
				res.data.errmsg
			);
			throw new Error(`dingTalk 旧版接口 业务响应错误 响应码：${res.data.errcode} 描述：${res.data.errmsg}`);
		}
	}

	/**
	 *新版规范api 检查响应结果
	 *
	 * @param {*} url
	 * @param {{ data: dingTalk.IResponseNewVersionGetAccessToken; status: number }} res
	 * @memberof DingTalkBasePublicService
	 */
	private nvcheckError(url, res: { data: any; status: number }) {
		if (res.status !== 200) {
			const data2 = res.data as Dingtalk.nv.response.IErrorResponse;
			this.app.coreLogger.error(
				"dingTalk 新版接口响应异常！\r\n请求的url：%s \r\n错误码 %s \r\n错误原因：%s",
				url,
				data2.code,
				data2.message || "未知原因"
			);
			throw new Error(`dingTalk 新版接口响应异常！ url:${url} code:${data2.code} message:${data2.message}`);
		}
	}
}
